﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpObjects.SearchEngine.Common.Search;

namespace SharpObjects.SearchEngine.Common.StatisticalAnalysis
{
    /// <summary>
    /// Calculates cosine similarity of two term vectors or characterizers
    /// </summary>
    public class CosineSimilarity : ISimilarity
    {
        /// <summary>
        /// Calculates the distance between two vectors: range 0 - 1
        /// </summary>
        /// <param name="vector1">Term vector 1</param>
        /// <param name="vector2">Term vector 2</param>
        /// <returns>Cosine distance between vectors in the range of 0 to 1</returns>
        public double CalculateDistance(List<TermVectorItem> vector1, List<TermVectorItem> vector2)
        {
            Dictionary<string, double> characterizer1 = new Dictionary<string, double>();
            Dictionary<string, double> characterizer2 = new Dictionary<string, double>();

            for (int k = 0; k < vector1.Count; k++)
            {
                if (characterizer1.ContainsKey(vector1[k].Term))
                {
                    characterizer1[vector1[k].Term] += vector1[k].Score;
                }
                else
                {
                    characterizer1.Add(vector1[k].Term, vector1[k].Score);
                }
            }

            for (int k = 0; k < vector2.Count; k++)
            {
                if (characterizer2.ContainsKey(vector2[k].Term))
                {
                    characterizer2[vector2[k].Term] += vector2[k].Score;
                }
                else
                {
                    characterizer2.Add(vector2[k].Term, vector2[k].Score);
                }
            }

            return CalculateDistance(characterizer1, characterizer2);
        }

        /// <summary>
        /// Calculates cosine distance between two vectors, represented by characterizers (dictionary of terms and their TF-IDFs): range 0 - 1
        /// </summary>
        /// <param name="characterizer1">Characterizer 1 - Dictionary[term, TF-IDF]</param>
        /// <param name="characterizer2">Characterizer 2 - Dictionary[term, TF-IDF]</param>
        /// <returns>Cosine distance between characterizers in the range of 0 to 1</returns>
        public double CalculateDistance(Dictionary<string, double> characterizer1, Dictionary<string, double> characterizer2)
        {
            double similarity = InnerProduct(characterizer1, characterizer2) / (VectorLength(characterizer1) * VectorLength(characterizer2));
            return similarity;
        }

        private static double InnerProduct(Dictionary<string, double> characterizer1, Dictionary<string, double> characterizer2)
        {
            double product = 0.0;

            List<string> allKeys = new List<string>();
            allKeys.AddRange(characterizer1.Keys);

            foreach (string key in characterizer2.Keys)
            {
                if (!characterizer1.ContainsKey(key))
                {
                    allKeys.Add(key);
                }
            }

            for (int k = 0; k < allKeys.Count(); k++)
            {
                string key = allKeys[k];

                double tfidf1 = 0.0;
                double tfidf2 = 0.0;

                if (characterizer1.ContainsKey(key))
                {
                    tfidf1 = characterizer1[key];
                }

                if (characterizer2.ContainsKey(key))
                {
                    tfidf2 = characterizer2[key];
                }

                product += tfidf1 * tfidf2;
            }

            return product;
        }

        private static double VectorLength(Dictionary<string, double> characterizer)
        {
            double sum = 0.0;

            foreach (string term in characterizer.Keys)
            {
                double tfidf = characterizer[term];

                sum += tfidf * tfidf;
            }

            return Math.Sqrt(sum);
        }

    }
}


// COSINE SIMILARITY
//
// a(xa, ya, za) and b(xb, yb, zb)
//
// sim = cos(alpha) = (xa * xb + ya * yb + za * zb) / (sqrt(xa*xa + ya*ya + za*za) * sqrt(xb*xb + yb*yb + zb*zb))
//
// if a characterizer has a missing coordinate - assume it to be 0
//

//public static double ComputeCosineSimilarity(double[] characterizer1, double[] characterizer2)
//{
//    if (characterizer1.Length != characterizer2.Length)
//    {
//        throw new ArgumentException("Vectors are of different lenght");
//    }

//    double denom = VectorLength(characterizer1) * VectorLength(characterizer2);

//    if (denom == 0.0)
//    {
//        return 0.0;
//    }
//    else
//    {
//        return InnerProduct(characterizer1, characterizer2) / denom;
//    }
//}

//public static double InnerProduct(double[] characterizer1, double[] characterizer2)
//{

//    if (characterizer1.Length != characterizer2.Length)
//    {
//        throw new ArgumentException("Vectors are of different lenght");
//    }

//    double result = 0.0;

//    for (int i = 0; i < characterizer1.Length; i++)
//    {
//        result += characterizer1[i] * characterizer2[i];
//    }

//    return result;
//}

//public static double VectorLength(double[] characterizer)
//{
//    double sum = 0.0;

//    for (int i = 0; i < characterizer.Length; i++)
//    {
//        sum = sum + (characterizer[i] * characterizer[i]);
//    }

//    return (double)Math.Sqrt(sum);
//}